package com.easyandroidanimations.library;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;

/**
 * This animation causes the view to flip horizontally to reveal another
 * user-provided view at the back of the original view. On animation end, the
 * original view is restored to its original state and is set to
 * <code>View.INVISIBLE</code>.
 * 
 * @author SiYao
 * 
 */
public class FlipHorizontalToAnimation extends Animation {

	public static final int PIVOT_CENTER = 0, PIVOT_LEFT = 1, PIVOT_RIGHT = 2;

	View flipToView;
	int pivot, direction;
	TimeInterpolator interpolator;
	long duration;
	AnimationListener listener;

	/**
	 * This animation causes the view to flip horizontally to reveal another
	 * user-provided view at the back of the original view. On animation end,
	 * the original view is restored to its original state and is set to
	 * <code>View.INVISIBLE</code>.
	 * 
	 * @param view
	 *            The view to be animated.
	 */
	public FlipHorizontalToAnimation(View view) {
		this.view = view;
		flipToView = null;
		pivot = PIVOT_CENTER;
		direction = DIRECTION_RIGHT;
		interpolator = new AccelerateDecelerateInterpolator();
		duration = DURATION_LONG;
		listener = null;
	}

	@Override
	public void animate() {
		ViewGroup parentView = (ViewGroup) view.getParent(), rootView = (ViewGroup) view
				.getRootView();

		float pivotX, pivotY, flipAngle = 270f, viewWidth = view.getWidth(), viewHeight = view
				.getHeight();
		final float originalRotationY = view.getRotationY();
		switch (pivot) {
		case PIVOT_LEFT:
			pivotX = 0f;
			pivotY = viewHeight / 2;
			break;
		case PIVOT_RIGHT:
			pivotX = viewWidth;
			pivotY = viewHeight / 2;
			break;
		default:
			pivotX = viewWidth / 2;
			pivotY = viewHeight / 2;
			flipAngle = 90f;
			break;
		}
		view.setPivotX(pivotX);
		view.setPivotY(pivotY);
		flipToView.setLayoutParams(view.getLayoutParams());
		flipToView.setLeft(view.getLeft());
		flipToView.setTop(view.getTop());
		flipToView.setPivotX(pivotX);
		flipToView.setPivotY(pivotY);
		flipToView.setVisibility(View.VISIBLE);

		while (parentView != rootView) {
			parentView.setClipChildren(false);
			parentView = (ViewGroup) parentView.getParent();
		}
		rootView.setClipChildren(false);

		AnimatorSet flipToAnim = new AnimatorSet();
		if (direction == DIRECTION_RIGHT) {
			flipToView.setRotationY(270f);
			flipToAnim.playSequentially(ObjectAnimator.ofFloat(view,
					View.ROTATION_Y, 0f, flipAngle), ObjectAnimator.ofFloat(
					flipToView, View.ROTATION_Y, 270f, 360f));
		} else {
			flipToView.setRotationY(-270f);
			flipToAnim.playSequentially(ObjectAnimator.ofFloat(view,
					View.ROTATION_Y, 0f, -flipAngle), ObjectAnimator.ofFloat(
					flipToView, View.ROTATION_Y, -270f, -360f));
		}
		flipToAnim.setInterpolator(interpolator);
		flipToAnim.setDuration(duration / 2);
		flipToAnim.addListener(new AnimatorListenerAdapter() {

			@Override
			public void onAnimationEnd(Animator animation) {
				view.setVisibility(View.INVISIBLE);
				view.setRotationY(originalRotationY);
				if (getListener() != null) {
					getListener()
							.onAnimationEnd(FlipHorizontalToAnimation.this);
				}
			}
		});
		flipToAnim.start();
	}

	/**
	 * @return The view to be revealed after flipping the original view.
	 */
	public View getFlipToView() {
		return flipToView;
	}

	/**
	 * @param flipToView
	 *            The view to set to be revealed after flipping the original
	 *            view.
	 * @return This object, allowing calls to methods in this class to be
	 *         chained.
	 */
	public FlipHorizontalToAnimation setFlipToView(View flipToView) {
		this.flipToView = flipToView;
		return this;
	}

	/**
	 * The available pivot points to flip are <code>PIVOT_CENTER</code>,
	 * <code>PIVOT_LEFT</code> and <code>PIVOT_RIGHT</code>.
	 * 
	 * @return The pivot point for flipping.
	 */
	public int getPivot() {
		return pivot;
	}

	/**
	 * The available pivot points to flip are <code>PIVOT_CENTER</code>,
	 * <code>PIVOT_LEFT</code> and <code>PIVOT_RIGHT</code>.
	 * 
	 * @param pivot
	 *            The pivot point to set for flipping.
	 * @return This object, allowing calls to methods in this class to be
	 *         chained.
	 */
	public FlipHorizontalToAnimation setPivot(int pivot) {
		this.pivot = pivot;
		return this;
	}

	/**
	 * The available flip directions are <code>DIRECTION_LEFT</code> and
	 * <code>DIRECTION_RIGHT</code>.
	 * 
	 * @return The direction of the flip.
	 * @see Animation
	 */
	public int getDirection() {
		return direction;
	}

	/**
	 * The available flip directions are <code>DIRECTION_LEFT</code> and
	 * <code>DIRECTION_RIGHT</code>.
	 * 
	 * @param direction
	 *            The direction of the flip to set.
	 * @return This object, allowing calls to methods in this class to be
	 *         chained.
	 * @see Animation
	 */
	public FlipHorizontalToAnimation setDirection(int direction) {
		this.direction = direction;
		return this;
	}

	/**
	 * @return The interpolator of the entire animation.
	 */
	public TimeInterpolator getInterpolator() {
		return interpolator;
	}

	/**
	 * @param interpolator
	 *            The interpolator of the entire animation to set.
	 */
	public FlipHorizontalToAnimation setInterpolator(
			TimeInterpolator interpolator) {
		this.interpolator = interpolator;
		return this;
	}

	/**
	 * @return The duration of the entire animation.
	 */
	public long getDuration() {
		return duration;
	}

	/**
	 * @param duration
	 *            The duration of the entire animation to set.
	 * @return This object, allowing calls to methods in this class to be
	 *         chained.
	 */
	public FlipHorizontalToAnimation setDuration(long duration) {
		this.duration = duration;
		return this;
	}

	/**
	 * @return The listener for the end of the animation.
	 */
	public AnimationListener getListener() {
		return listener;
	}

	/**
	 * @param listener
	 *            The listener to set for the end of the animation.
	 * @return This object, allowing calls to methods in this class to be
	 *         chained.
	 */
	public FlipHorizontalToAnimation setListener(AnimationListener listener) {
		this.listener = listener;
		return this;
	}

}
